magiclanternfandomcom-20200223-history
GPL Tools/ARM console/API
API reference for ARM firmware analysis console Examples are given for the disassembly of autoexec.bin (550D, 1.0.8) and for the 550D dump, which can be obtained by running Magic Lantern on your camera. Loading some dumps and some names In 1: D = load_dumps("(autoexec|108)") Did not find the matching bin for mynames.idc. Ignoring this file. Did not find the matching bin for mychanges.idc. Ignoring this file. Input files: Binary dump (*.bin) LoadAddr IDC database (*.idc) 550d.108.0xff010000.bin FF010000 550d.108.20101116_indy_ROM0.idc autoexec.0x8A000.bin 8A000 n/a Disassembling 550d.108.0xff010000.bin ... ok Disassembling autoexec.0x8A000.bin <8a000>... ok Parsing 550d.108.20101116_indy_ROM0.idc... found 56768 MakeName's and 18053 MakeFunction's Parsing disassembly of 550d.108.0xff010000.bin... found 1395198 lines Indexing references... Parsing disassembly of autoexec.0x8A000.bin... found 19564 lines Indexing references... In 2: t2i,ml = D # D is sorted by bin file name In 3: ml.load_names("stubs-550d.108.S") Found 80 stubs in stubs-550d.108.S. In 4: ml.load_names("autoexec.S") Found 8 stubs in autoexec.S. Magic commands Shortcuts to misc stuff. They operate on the currently selected dump, so first you have to select one: In 5: sel ml Go to address or name: In 6: g 8B194 8b194: e59fb19c ldr r11, #412 ; 0x8b338: pointer to 0xc2b8c 8b198: e59fc19c ldr r12, #412 ; 0x8b33c: pointer to 0x8B7F4 (bmp_printf) 8b19c: e58b0000 str r0, r11 8b1a0: e1a01006 mov r1, r6 8b1a4: e3a02028 mov r2, #40 ; 0x28 8b1a8: e3a00802 mov r0, #131072 ; 0x20000 8b1ac: e59f318c ldr r3, #396 ; **'ML v. %s (%s)\nBuilt on %s by %s\n' 8b1b0: e88d0090 stm sp, {r4, r7} 8b1b4: e58dc010 str r12, #16 8b1b8: e58d8008 str r8, #8 In 7: g fprintf NSTUB(fprintf, 8eec0): 8eec0: e92d000e push {r1, r2, r3} 8eec4: e92d4070 push {r4, r5, r6, lr} 8eec8: e24ddf41 sub sp, sp, #260 ; 0x104 8eecc: e28dcf46 add r12, sp, #280 ; 0x118 8eed0: e1a05000 mov r5, r0 8eed4: e1a0300c mov r3, r12 8eed8: e59d2114 ldr r2, #276 8eedc: e58dc100 str r12, #256 8eee0: e1a0000d mov r0, sp 8eee4: e59fc03c ldr r12, #60 ; 0x8ef28: pointer to 0xFF1D6638 (vsnprintf) Search for strings: In 8: s magic finding strings... 91b1c: 'A:/magic.cfg' 916b8: 'magic lantern init done' 91628: 'B:/magic.cfg' 91ff8: 'Magic Lantern install' 917d4: '# Magic Lantern %s (%s)\n# Build on %s by %s\n' 915fc: 'Magic Lantern %s (%s)' 915f0: 'MAGIC ' Search for references: In 9: r additional_version ROMBASE+0x10f8: 8b0f8: e59f3220 ldr r3, #544 ; 0x8b320: pointer to 0x15094 (additional_version) 0x15094 (additional_version) In 10: r 0x1ED0 ROMBASE+0x5b54: 8fb54: e59f40bc ldr r4, #188 ; 0x8fc18: pointer to 0x1ED0 (sounddev) 0x1ED0 (sounddev) Classes/objects Dump Contains all info about a dump. In 11: ml.bin Out11: autoexec.0x8A000.bin In 12: hex(ml.loadaddr), hex(ml.minaddr), hex(ml.maxaddr) Out12: ('8A000', '8A000', 'B30BC') In 13: t2i.funcs(r"FlavorS") # when ratio=1, it uses a regex search ff205b24: FlavorSharpness ff205c14: FlavorContrast ff205dac: FlavorSaturation ff205e9c: FlavorColorTone In 14: t2i.funcs("DebugMsg", 0.5) # when ratio < 1, this is the min. allowed ratio for fuzzy search ff0673ec: DebugMsg ff2da3e0: _DebugSignal ff08b350: EnableDebugMon ff0cbac8: DpSetDebugMode In 15: t2i.refs("sounddev") ... sounddev_task+28: ff053488: e51f426c ldr r4, #-620 ; 0xff053224: pointer to 0x1ED0 (sounddev) 0x1ED0 (sounddev) ... In 16: t2i.refs("sounddev", context=2) ... sounddev_task+28: ff053480: e3a01000 mov r1, #0 ; 0x0 ff053484: eb006b5e bl @create_binary_semaphore ff053488: e51f426c ldr r4, #-620 ; 0xff053224: pointer to 0x1ED0 (sounddev) ff05348c: e3a02000 mov r2, #0 ; 0x0 ff053490: e5840058 str r0, #88 0x1ED0 (sounddev) ... In 17: t2i.strings("AudioLevel") finding strings... ff544628: 'AudioLevelStateSignature' ff1ab830: 'SoundDevice\\AudioLevel.c' ff544670: 'AudioLevel' In 18: t2i.strrefs("^CreateTask$") String references to ff06e2c8 'CreateTask': createTask_maybe+68: ff06e158: 028f0f5a addeq r0, pc, #360 ; *'CreateTask' 'CreateTask' In 19: t2i.disasm(0xff010000, 0xff01000f) ff010000: e59ff0bc ldr pc, #188 ; 0xff0100c4: pointer to 0xff01000c NSTUB($ fr603e.var_4C, ff010004): ff010004: 6e6f6167 powvsez f6, f7, f7 ff010008: 796f7369 stmdbvc pc!, {r0, r3, r5, r6, r8, r9, r12, sp, lr}^ ff01000c: e3a00103 mov r0, #-1073741824 ; 0xc0000000 In 20: ml.MakeName(0x8D070, "config_parse_file") In 21: ml.MakeFunction(0x8D070) # will guess the end address Size: 108 In 22: ml.MakeFunction(0x8D070, 0x8D0DC) # explicit end address In 23: ml.MakeFunction(0x8D070, 0x8D0DC, "config_parse_file") # explicit end address and name Overwriting name config_parse_file In 24: ml.MakeFunction(0x8D070, name="config_parse_file") # will guess the end address and set the given name Overwriting name config_parse_file Fun ASM function. Constructor: *'dump.Fun(name_or_addr)' In 25: t2i.Fun(0xFF1C924C) Out25: ASM function: dispcheck at 0xff1c924c in 550d.108.0xff010000.bin In 26: f = t2i.Fun("setFiltreOff") Unknown function: setFiltreOff. Using closest match: SetFilterOff. In 27: f''' Out27: ASM function: SetFilterOff at 0xff064e98 in 550d.108.0xff010000.bin In 28: '''f.name Out28: SetFilterOff In 29: "%x"%f.addr Out29: ff064e98 In 30: "%x"%f.end Out30: ff064eb8 In 31: f.size Out31: 32 In 32: f.called_by() ff064d30: UnpowerMicAmp+40 ff064120: sub_FF064114+12 In 33: f.calls() ff064ea8: eb00094f bl @DebugMsg ff064eb4: eafffb4e b @audio_ic_write // End of function: SetFilterOff In 34: f.disasm() // Start of function: SetFilterOff NSTUB(SetFilterOff, ff064e98): ff064e98: e92d4010 push {r4, lr} ff064e9c: e28f20f4 add r2, pc, #244 ; *'SetFilterOff' ff064ea0: e3a01003 mov r1, #3 ; 0x3 ff064ea4: e3a00014 mov r0, #20 ; 0x14 ff064ea8: eb00094f bl @DebugMsg ff064eac: e8bd4010 pop {r4, lr} ff064eb0: e3a00c31 mov r0, #12544 ; 0x3100 ff064eb4: eafffb4e b @audio_ic_write // End of function: SetFilterOff In 35: f.sig Out35: push add mov mov bl pop mov b In 36: f.refs() SetFilterOff+4: ff064e9c: e28f20f4 add r2, pc, #244 ; *'SetFilterOff' 'SetFilterOff' ... In 37: f.strings() SetFilterOff Modules match Matches functions and addresses between different versions of firmware, or different camera models. See GPL Tools/match.py. guessfunc This guesses function locations from calls (BL, BX), PUSH instructions, and loaded names which are not marked as functions. Function size is guessed with emusym.GuessFunction. This is the main function. I prefer to run this before generating the HTML output. In 38: guessfunc.run(ml) Function 0xff82399c is outside ROM => skipping Function 0xff8922a4 is outside ROM => skipping ... Those are little workers: In 39: analyze_names(dump) In 40: analyze_push(dump) In 41: analyze_bl(dump) In 42: analyze_bx(dump) html Exports the disassembly to a (huge) set of HTML files, which can be browsed offline (like this example). It can also do some automated firmware analysis. In 43: html.quick(ml) # quick disassembly of dump ml Disassembling autoexec.0x8A000.bin... ... Raw disassembly... done, ETA 0:00:05... In 44: html.full(ml) # quick(...) + symbolic function analysis Disassembling autoexec.0x8A000.bin... ... Running symbolic analysis for autoexec.0x8A000.bin... ... Function analysis... done, ETA 0:00:19... In 45: html.quick(D) # process all dumps from list D In 46: html.full(D) In 47: html.update(ml) # only re-generate modified pages (experimental). Some small workers: In 48: html.link2addr(0x8B194) Out48: 8B194 In 49: html.link2func(ml.Fun("fprintf")) Out49: fprintf In 50: html.link2funcoff(0x8eee0) disasm Disassembly and code browsing. In 51: hex(BYTE(ml, 0x916b8)) Out51: 6D In 52: hex(UINT32(ml, 0x916b8)) Out52: 6967616D In 53: GuessString(ml, 0x916b8) Out53: magic lantern init done In 54: funcname(ml, 0x8eec0) Out54: fprintf In 55: funcname(ml, 0x8b000) Out55: sub_8B000 In 56: dataname(ml, 0x1ED0) Out56: 0x1ED0 (sounddev) In 57: guess_data(ml,0x8c228) Out57: 0x8c228: pointer to 0x2E5B0 (bmp_vram_info) In 58: guess_data(ml,0x8e748) Out58: 0x8e748: pointer to 'They try to set code to %d' In 59: guess_data(ml,0x8dca8) Out59: @menu_add fileutil Utils for working with files. In 60: change_ext("foo.py", ".jpg") Out60: foo.jpg s = capture(func, *args, **kwargs) # capture output and result of func stats Sorts functions by number of calls to them: In 61: stats.calls_to(t2i) ... 1753: dialog_label_item 2555: assert_0 16694: DebugMsg Or by how many other functions they call: In 62: stats.calls_from(t2i) ... 103: sub_FF300BA0 sub_FF2FF400,JudgeBottomInfoDispT ... 133: sub_FF0340B4 release_mem,sub_FF031E08,LVCAF_Re ... 215: IDLEHandler release_mem,EndMovieRecSequence_N ... The most called functions seem to be DebugMsg and assert. idapy IDAPython compatibility layer. Also contains functions from IDAPython/utils.py and other small stuff. They are NOT fully compatible with IDAPython! I only wrote it to help porting my existing scripts. The first thing you have to do before using any of those functions is: In 63: select_dump(dump) where dump is a Dump object; for example: In 64: select_dump(t2i) Or use the magic command sel, which does the same thing. In 65: GetFirstWord("abc def") Out65: abc In 66: getRegs03("R1, R2 and maybe R5") Out66: 2 In 67: getRegsS("R1, R2 and maybe R5") Out67: 'R2', 'R5' In 68: filter_non_printable("\x07buzz!") Out68: buzz! In 69: print GetDisasm(0xff064bbc) subeq r1, pc, #2944 ; 0xb80 In 70: GetMnem(0xff064bbc) Out70: SUB In 71: GetMnef(0xff064bbc) Out71: SUBEQ In 72: GetOpnd(0xff064bbc, 0) Out72: R1 In 73: GetOpnd(0xff064bbc, 1) Out73: PC In 74: GetOpnd(0xff064bbc, 2) Out74: #2944 In 75: GetOpType(0xff064bbc, 0) Out75: 1 In 76: GetOpType(0xff064bbc, 1) Out76: 1 In 77: GetOpType(0xff064bbc, 2) Out77: 5 In 78: GetOperandValue(0xff064bbc, 0) Out78: 1 In 79: GetOperandValue(0xff064bbc, 1) Out79: 15 In 80: GetOperandValue(0xff064bbc, 2) Out80: 2944 In 81: print GetDisasm(0xff48e5a4) stmiane r5!, {r6, r10, r12, lr} In 82: GetModeSuffix(0xff48e5a4) Out82: IA In 83: GetCondSuffix(0xff48e5a4) Out83: NE In 84: OppositeSuffix("EQ") Out84: NE In 85: print GetDisasm(0xff064b90) ldrb r0, #16 In 86: GetExtraSuffixes(0xff064b90) Out86: B In 87: GetByteSuffix(0xff064b90) Out87: B In 88: GetHalfwordSuffix(0xff064b90) In 89: GetFlagSuffix(0xff064b90) In 90: print GetDisasm(0xff064bb4) cmpne r0, #5 ; 0x5 In 91: ChangesFlags(0xff064bb4) Out91: True In 92: GetString(0xff011e1c) Out92: akashimorino In 93: isFuncStart(0xff064e98) Out93: True In 94: GetFunctionName(0xff064e98) Out94: SetFilterOff In 95: GetFuncOffset(0xff064e98) Out95: SetFilterOff In 96: FuncItems(0xff064e98) Out96: 4278603420L, 4278603424L, 4278603428L, 4278603432L, 4278603436L, 4278603440L, 4278603444L In 97: SegStart(0xff064e98) Out97: 4278255616 In 98: SegEnd(0xff064e98) Out98: 4283881244 In 99: CodeRefsTo(0xff064e98) Out99: 4278599968L In 100: DataRefsTo(1ED0) ... In 101: CodeRefsFrom(0xff064e98) Out101: 4278598644L In 102: DataRefsFrom(0xff064e98) Out102: 1182033235, 3, 20, 3912040463L, 12544, 3912056945L bunch Something like a struct. In 103: b = Bunch(foo=5, baz="hello") In 104: b.foo Out104: 5 In 105: b.baz Out105: hello emusym Symbolic emulation of ASM code, based on SymPy. Theory is here: IDAPython/Static_analysis These functions operate on the currently selected dump, therefore you have to select one first. In 106: sel ml In 107: CP = emusym.find_code_paths(0x8dac8) # extract code path by doing a recursive branch analysis In 108: emusym.create_graph(CP, "myfunc.svg") # generate a graph of code flow, with graphviz In 109: cp = CP0 # select first code path In 110: emusym.resetArm() # reset the symbolic ARM registers and memory contents In 111: emusym.emusym_code_path(cp) # run one code paths under symbolic emulation In 112: emusym.emusym_code_path(cp, codetree=True) # return a code tree, useful for decompiling Out112: SEQ(IF(MEM(8 + MEM(unk_R3)), IFB(NE, SEQ(IF(MEM(4 + MEM(unk_R3)), IFB(TRUE, SEQ(MEMWRITE( ... In 113: emusym.split_code_path_ea_cond(cp) # split the code path in two plain lists Out113: (580300, 580308, 580312, 580316, 580320, 580328, 580332, 580336, 580344, 580344, [[], [], 'NE', 'NE', 'NE', [], 'EQ', 'EQ', 'EQ', 'EQ', 'EQ']) In 114: emusym.GuessFunction(0x8ea48) # guesses the end of the function, when you know the start Size: 424 Out114: 584684 bkt Backtracing: solves the contents of ARM registers / memory addresses by symbolic emulation of ASM code, going backwards until all requested unknowns are found. Theory is here: IDAPython/Backtracing. In 115: ea = 0x8CE98 In 116: g ea-28 8ce7c: e1a0700a mov r7, r10 8ce80: e3a00032 mov r0, #50 ; 0x32 8ce84: e3a01003 mov r1, #3 ; 0x3 8ce88: e59f2090 ldr r2, #144 ; **'%s: ERROR Deleting config' 8ce8c: e59f3074 ldr r3, #116 ; **'config_parse' 8ce90: e59fa074 ldr r10, #116 ; 0x8cf0c: pointer to 0xFF0673EC (DebugMsg) 8ce94: e1a0e00f mov lr, pc 8ce98: e12fff1a bx r10 8ce9c: e3570000 cmp r7, #0 ; 0x0 8cea0: 159f507c ldrne r5, #124 ; 0x8cf24: pointer to 0xFF0182CC (free) In 117: bkt.back_solve(ea, 'ARM.R0','ARM.R1') # a faster heuristic Out117: 3 In 118: bkt.back_solve_slow(ea, 'ARM.R0','ARM.R1') # slow, dumb, but reliable Out118: 3 In 119: bkt.go_back(ea) Out119: 577176 In 120: bkt.go_back(ea-4,ea) Out120: 577172, 577176 In 121: bkt.find_func_call(ea, 4) Out121: (4278612972L, 'DebugMsg', "(0x32, 3, '%s: ERROR Deleting config', 'config_parse')") In 122: bkt.trace_calls_to("DebugMsg", 4) Function found at ff0673ec Out122: {570628: (4278612972L, 'DebugMsg', "(0x12, 3, '%s created (and exiting)', 'null_task')"), ... ... These functions find the function which is called at a given address, using backtracing if needed. In 123: bkt.subaddr_bl(0x8CD74) Out123: 575428 In 124: bkt.subaddr_bx(0x8CE98) Out124: 4278612972 In 125: bkt.subaddr_mov(addr) # for MOV PC, ... deco Experimental decompiler based on SymPy (see module emusym). Only works for functions without loops. In 126: print deco.decompile(0x8D070) *(-4 + sp0) = lr0 *(-8 + sp0) = unk_R6 *(-12 + sp0) = unk_R5 *(-16 + sp0) = unk_R4 FIO_Open(arg0, 0x1000, arg2, ...) => ret_FIO_Open_8D084 strcpy(0xb2818, arg0, arg2, ...) => ret_strcpy_8D0A0 if ret_FIO_Open_8D084 -1: *(0xb27d4) = 0xb2854 return 0xb27d4 config_parse(ret_FIO_Open_8D084, arg0, arg2, strcpy) => ret_config_parse_8D0B8 FIO_CloseFile(handle=ret_FIO_Open_8D084) => ret_FIO_CloseFile_8D0CC *(0xb27d4) = ret_config_parse_8D0B8 return 0xb27d4 !end cache Stores the result of lengthy computations. In 127: cache.access("my_computation", func_which_takes_ages_to_run) In 128: cache.disable() In 129: cache.enable() In 130: cache.clear() There is also some experimental support for persistent cache, but it's disabled by default. progress Simple progress indicator; also computes ETA. In 131: progress("Doing some useless stuff...") In 132: time.sleep(1) In 133: progress(0.25) Doing some useless stuff... done, ETA 0:00:03... In 134: time.sleep(2) In 135: progress(0.75) Doing some useless stuff... done, ETA 0:00:01... doc Make Wiki docs from IPython commands included in main text. Something like auto-generating docs from docstrings. You write: This is an example: 2+2 and you get: This is an example: In 1: 2+2 Out1: 4 All the code to be executed in IPython is prefixed by: * four spaces: normal code, i.e. show input command, run it and show output. * three spaces and %: show input command, run it, but do not show output. * three spaces and ~: show input command, but do not run it. * three spaces and #: run the command, but do not show anything. * three spaces and of indices: display only those lines of output; use a string instead of index to display anything you like. e.g.: 1,2,"...",-1ml.strings displays the first line, second line, ellipsis and then the last line. Also, in this mode, max line length is limited to 100. Spacing is critical, so pay attention! Also, 4 spaces and % is a magic command. To make a doc: doc.run("scripts/doc/api-ref.wikipy") Result is in scripts/doc/api-ref.wiki, with Wiki markup codes. ---- Enjoy! --Alexdu 16:56, December 1, 2010 (UTC)